package cloudspace.vm.io.console;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * This class is used for Memory Managed Line caches. Basically, the cache uses
 * a circular array underneath to store lines and drop them off the back end of
 * the array once the memory cache has reached its max size. To allow other
 * classes to access these lines without understanding the fact that lines are
 * removed automatically, the cache implements an overall index. This counter is
 * started from the creation of the cache and only flushed when it reaches
 * Long.MAX_SiZE. If this happens, a FlushException is thrown. This class is
 * based on a list; however, there are alot of functions not supported in this
 * cache therefore it does not implement the interface.
 * 
 * @author Mike Woods
 * 
 */
public class MemoryManagedLineCache
{
	private InfoStreamLine[] backend;
	private int startIndex;
	private int tailIndex;
	private int max;
	private int size;
	private long lowIndex;

	/**
	 * This returns the lowest stored index in the cache.
	 * 
	 * @return
	 */
	public long getStartIndex()
	{
		return lowIndex;
	}

	private void incTail()
	{
		if (tailIndex == startIndex && size == max)
		{
			startIndex = (startIndex + 1) % max;
			lowIndex++;
		}
		tailIndex = (tailIndex + 1) % max;

	}

	private void incSize()
	{
		if (size < max)
		{
			size++;
		}
	}

	private int convertIndex(long userIndex)
	{
		long reducedIndex = userIndex - lowIndex;
		return (int) (reducedIndex + startIndex) % max;
	}

	/**
	 * Create a new Managed Cache with a specified maximum storage size.
	 * 
	 * @param maxSize
	 *            the maximum size of the cache underneath.
	 */
	public MemoryManagedLineCache(int maxSize)
	{
		max = maxSize;
		init();
	}

	private void init()
	{
		backend = new InfoStreamLine[max];
		startIndex = 0;
		tailIndex = 0;
		size = 0;
	}

	/**
	 * Add a line to the cache
	 * 
	 * @param e
	 *            line to add
	 * @return true if the line was added.
	 */
	public synchronized boolean add(InfoStreamLine e)
	{
		backend[tailIndex] = e;
		incTail();
		incSize();
		return true;
	}

	/**
	 * Adds a collection of Lines to the cache.
	 * 
	 * @param c
	 *            the collection to add
	 * @return true if the line were added.
	 */
	public synchronized boolean addAll(Collection<? extends InfoStreamLine> c)
	{
		Iterator<? extends InfoStreamLine> iter = c.iterator();
		while (iter.hasNext())
		{
			backend[tailIndex] = iter.next();
			incTail();
			incSize();
		}
		return true;
	}

	/**
	 * Test to see if the cache is empty
	 * 
	 * @return true if the cache is empty
	 */
	public synchronized boolean isEmpty()
	{
		return size == 0;
	}

	/**
	 * Access an iterator for the entire cache.
	 * 
	 * @return an iterator for all of the elements stored in the cache.
	 */
	public synchronized Iterator<InfoStreamLine> iterator()
	{
		InfoStreamLine[] reorder = new InfoStreamLine[size];
		long realIndex = lowIndex;
		for (int i = 0; i < size; i++)
		{
			reorder[i % size] = backend[convertIndex(realIndex)];
			realIndex++;
		}
		return Arrays.asList(reorder).iterator();
	}

	/**
	 * Returns the actual max size of the list. This takes into account the
	 * number of elements that have been dropped from the cache.
	 * 
	 * @return the number of elements that have been stored and are stored in
	 *         the cache
	 */
	public synchronized long size()
	{
		return lowIndex + size;
	}

	/**
	 * Gets the element stored at the specified index
	 * 
	 * @param index
	 *            the index of the item.
	 * @return the info stream line that was stored at the given index.
	 */
	public synchronized InfoStreamLine get(long index)
	{
		if (index < lowIndex)
		{
			throw new ArrayIndexOutOfBoundsException(
					"The index passed to get is too low.");
		}
		if (index > max)
		{
			throw new ArrayIndexOutOfBoundsException(
					"The index passed to get is too high.");
		}
		return backend[convertIndex(index)];
	}

	/**
	 * Gets the sublist at the specified indexes
	 * 
	 * @param fromIndex
	 *            the start index for the get command
	 * @param toIndex
	 *            the end index of the start command.
	 * @return the sub list
	 */
	public synchronized List<InfoStreamLine> subList(long fromIndex,
			long toIndex)
	{
		long from = fromIndex;
		if (from < lowIndex)
		{
			from = lowIndex;
		}
		if (toIndex > lowIndex + size)
		{
			toIndex = max;
		}
		List<InfoStreamLine> subList = new ArrayList<InfoStreamLine>();
		for (long i = from; i < toIndex; i++)
		{
			int realIndex = convertIndex(i);
			subList.add(backend[realIndex]);
		}
		return subList;
	}

	/**
	 * Returns a sorted array of the circular array stored underneath.
	 * 
	 * @return the sorted array of lines.
	 */
	public synchronized Object[] toArray()
	{
		Object[] reorder = new Object[size];
		for (int i = 0; i < size; i++)
		{
			reorder[i] = backend[convertIndex(i)];
		}
		return reorder;
	}

}